module("DarkRP", package.seeall)

-- Variables that maintain the existing stubs and hooks
local stubs = {}
local hookStubs = {}

-- Contains the functions that the hooks call by default
hooks = {}

-- Delay the calling of methods until the functions are implemented
local delayedCalls = {}

local returnsLayout, isreturns
local parmeterLayout, isparameters
local isreturns
local checkStub

local hookLayout

local realm -- State variable to manage the realm of the stubs

/*---------------------------------------------------------------------------
Methods that check whether certain fields are valid
---------------------------------------------------------------------------*/
isreturns = function(tbl)
	if not istable(tbl) then return false end
	for k,v in pairs(tbl) do
		if not checkStub(v, returnsLayout) then return false end
	end
	return true
end

isparameters = function(tbl)
	if not istable(tbl) then return false end
	for k,v in pairs(tbl) do
		if not checkStub(v, parmeterLayout) then return false end
	end
	return true
end

/*---------------------------------------------------------------------------
The layouts of stubs
---------------------------------------------------------------------------*/
local stubLayout = {
	name = isstring,
	description = isstring,
	parameters = isparameters, -- the parameters of a method
	returns = isreturns, -- the return values of a method
	metatable = istable -- DarkRP, Player, Entity, Vector, ...
}

local hookLayout = {
	name = isstring,
	description = isstring,
	parameters = isreturns, -- doesn't have the 'optional' field
	returns = isreturns,
}

returnsLayout = {
	name = isstring,
	description = isstring,
	type = isstring
}

parmeterLayout = {
	name = isstring,
	description = isstring,
	type = isstring,
	optional = isbool
}

/*---------------------------------------------------------------------------
Check the validity of a stub
---------------------------------------------------------------------------*/
checkStub = function(tbl, stub)
	if not istable(tbl) then return false, "table" end

	for name, check in pairs(stub) do
		if not check(tbl[name]) then
			return false, name
		end
	end

	return true
end

/*---------------------------------------------------------------------------
When a stub is called, the calling of the method is delayed
---------------------------------------------------------------------------*/
local function notImplemented(name, args)
	delayedCalls[name] = delayedCalls[name] or {}
	table.insert(delayedCalls[name], args)

	return nil -- no return value because the method is not implemented
end

/*---------------------------------------------------------------------------
Generate a stub
---------------------------------------------------------------------------*/
function stub(tbl)
	local isStub, field = checkStub(tbl, stubLayout)
	if not isStub then
		error("Invalid DarkRP method stub! Field \"" .. field .. "\" is invalid!", 2)
	end

	tbl.realm = realm
	stubs[tbl.name] = tbl

	return function(...) return notImplemented(tbl.name, {...}) end
end

/*---------------------------------------------------------------------------
Generate a hook stub
---------------------------------------------------------------------------*/
function hookStub(tbl)
	local isStub, field = checkStub(tbl, hookLayout)
	if not isStub then
		error("Invalid DarkRP hook! Field \"" .. field .. "\" is invalid!", 2)
	end

	tbl.realm = realm
	hookStubs[tbl.name] = tbl
end

/*---------------------------------------------------------------------------
Retrieve the stubs
---------------------------------------------------------------------------*/
function getStubs()
	return table.Copy(stubs)
end

/*---------------------------------------------------------------------------
Retrieve the hooks
---------------------------------------------------------------------------*/
function getHooks()
	return table.Copy(hookStubs)
end

/*---------------------------------------------------------------------------
Call the cached methods
---------------------------------------------------------------------------*/
function finish()
	local calls = table.Copy(delayedCalls) -- Loop through a copy, so the notImplemented function doesn't get called again
	for name, log in pairs(calls) do
		if not stubs[name] then ErrorNoHalt("Calling non-existing stub \"" .. name .. "\"") continue end

		for _, args in pairs(log) do
			stubs[name].metatable[name](unpack(args))
		end
	end

	delayedCalls = {}
end

/*---------------------------------------------------------------------------
Load the interface files
---------------------------------------------------------------------------*/
local function loadInterfaces()
	local root = GM.FolderName .. "/gamemode/modules"

	local _, folders = file.Find(root.."/*", "LUA")

	ENTITY = FindMetaTable("Entity")
	PLAYER = FindMetaTable("Player")
	VECTOR = FindMetaTable("Vector")

	for _, folder in SortedPairs(folders, true) do
		local interfacefile = string.format("%s/%s/%s_interface.lua", root, folder, "%s")
		local client = string.format(interfacefile, "cl")
		local shared = string.format(interfacefile, "sh")
		local server = string.format(interfacefile, "sv")

		if file.Exists(shared, "LUA") then
			if SERVER then AddCSLuaFile(shared) end
			realm = "Shared"
			include(shared)
		end

		if SERVER and file.Exists(client, "LUA") then
			AddCSLuaFile(client)
		end

		if SERVER and file.Exists(server, "LUA") then
			realm = "Server"
			include(server)
		end

		if CLIENT and file.Exists(client, "LUA") then
			realm = "Client"
			include(client)
		end
	end

	ENTITY, PLAYER, VECTOR = nil, nil, nil
end
loadInterfaces()